既然MVC都有了,就繼續調整其他程式,把架構慢慢放進去。
改完了index.php,之後就把做好的架構套用到其他程式上。以forum.php為例,他的作用是顯示單一論壇內的文章列表。
<?php
session_start();
if(isset($_SESSION['user']['account'])) {
$member = true;
$name = $_SESSION['user']['name'];
} else $member = false;
$conn = mysql_connect('localhost', 'root', '');
if(!$conn)
die('mysql connection error.');
mysql_select_db('myforum');
$forum_id = mysql_real_escape_string($_GET['id']);
if(isset($_SESSION['msg'])) {
$message = mysql_real_escape_string($_SESSION['msg']);
unset($_SESSION['msg']);
}
$sql = "SELECT * FROM forums WHERE id=$forum_id";
$result = mysql_query($sql, $conn);
$forum = mysql_fetch_array($result);
?>
<table width="800" border="1" cellpadding="0" cellspacing="0" align="center">
<!-- header start -->
<tr>
<th align="left"><a href="index.php">我的論壇</a> - <?php echo $forum['name'];?></th>
</tr>
<tr>
<td style="text-align:right" bgcolor="#336699">
<table align="right">
<tr>
<?php
if($member) {
?>
<td>Welcome [<?php echo $name;?>] <button onclick='document.location.href="logout.php"'>登出</button></td>
<?php
} else {
?>
<form method="post" action="login.php">
<td>登入後可留言 </td>
<td>帳號</td>
<td><input type="text" name="account"></td>
<td>密碼</td><td><input type="password" name="password"></td>
<td><input type="submit" value="登入"></td>
</form>
<?php
}
?>
</tr>
</table>
</td>
</tr>
<!-- header end -->
<!-- content start-->
<tr height="600">
<td valign="top">
<table width="100%" border="1" cellspacing="0" cellpadding="5">
<tr bgcolor="#DDEEFF">
<th>標題</th>
<th>作者</th>
<th>發表時間</th>
<th>操作</th>
</tr>
<?php
$sql = "SELECT a.id, a.title, a.create_time, u.name FROM articles a LEFT JOIN users u ON u.id=a.users_id WHERE forums_id=$forum_id";
$result = mysql_query($sql, $conn);
$count = 0;
while($row = mysql_fetch_array($result)) {
if($count%2==0) {
$style = 'bgcolor="#EEFFEE"';
} else {
$style = 'bgcolor="#FFFFFF"';
}
?>
<tr <?php echo $style;?>>
<td><?php echo $row['title'];?></td>
<td><?php echo $row['name'];?></td>
<td><?php echo $row['create_time'];?></td>
<td>
<button onclick="document.location.href='article.php?id=<?php echo $row['id'];?>'">進入</button>
</td>
</tr>
<?php
$count++;
}
?>
</table>
</td>
</tr>
<!-- content end -->
<!-- footer start -->
<tr bgcolor="#336699">
<td align="center">
<font color="#EFEFEF">Copyright 1899 by Fillano</font>
</td>
</tr>
<!-- footer end -->
</table>
從瀏覽器看:
可以看到,這支程式在套用MVC架構前,就跟原本的index.php一樣,資料、頁面的邏輯混雜在一起,要修改很困難。根據之前的步驟,第一步把view切開,不過我們一小步一小步走,先拆成兩個php:
把view拆出以後的forum.php:
<?php
session_start();
if(isset($_SESSION['user']['account'])) {
$member = true;
$name = $_SESSION['user']['name'];
} else $member = false;
$conn = mysql_connect('localhost', 'root', '');
if(!$conn)
die('mysql connection error.');
mysql_select_db('myforum');
$forum_id = mysql_real_escape_string($_GET['id']);
if(isset($_SESSION['msg'])) {
$message = mysql_real_escape_string($_SESSION['msg']);
unset($_SESSION['msg']);
}
$sql = "SELECT * FROM forums WHERE id=$forum_id";
$result = mysql_query($sql, $conn);
$forum = mysql_fetch_array($result);
$sql = "SELECT a.id, a.title, a.create_time, u.name FROM articles a LEFT JOIN users u ON u.id=a.users_id WHERE forums_id=$forum_id";
$result = mysql_query($sql, $conn);
$count = 0;
$rows = array();
while($row = mysql_fetch_array($result)) {
if($count%2==0) {
$style = 'bgcolor="#EEFFEE"';
} else {
$style = 'bgcolor="#FFFFFF"';
}
$row['style'] = $style;
$rows[] = $row;
$count++;
}
include 'views/forum.php';
拆到views裡面的forum.php:
<table width="800" border="1" cellpadding="0" cellspacing="0" align="center">
<!-- header start -->
<tr>
<th align="left"><a href="index.php">我的論壇</a> - <?php echo $forum['name'];?></th>
</tr>
<tr>
<td style="text-align:right" bgcolor="#336699">
<table align="right">
<tr>
<?php
if($member) {
?>
<td>Welcome [<?php echo $name;?>] <button onclick='document.location.href="logout.php"'>登出</button></td>
<?php
} else {
?>
<form method="post" action="login.php">
<td>登入後可留言 </td>
<td>帳號</td>
<td><input type="text" name="account"></td>
<td>密碼</td><td><input type="password" name="password"></td>
<td><input type="submit" value="登入"></td>
</form>
<?php
}
?>
</tr>
</table>
</td>
</tr>
<!-- header end -->
<!-- content start-->
<tr height="600">
<td valign="top">
<table width="100%" border="1" cellspacing="0" cellpadding="5">
<tr bgcolor="#DDEEFF">
<th>標題</th>
<th>作者</th>
<th>發表時間</th>
<th>操作</th>
</tr>
<?php
foreach($rows as $row) {
?>
<tr <?php echo $row['style'];?>>
<td><?php echo $row['title'];?></td>
<td><?php echo $row['name'];?></td>
<td><?php echo $row['create_time'];?></td>
<td>
<button onclick="document.location.href='article.php?id=<?php echo $row['id'];?>'">進入</button>
</td>
</tr>
<?php
}
?>
</table>
</td>
</tr>
<!-- content end -->
<!-- footer start -->
<tr bgcolor="#336699">
<td align="center">
<font color="#EFEFEF">Copyright 1899 by Fillano</font>
</td>
</tr>
<!-- footer end -->
</table>
拆完以後確定可以執行,畫面也沒問題,這時就可以把view從php+html改成Twig。根據index的經驗,實際上會用到的其實只有一部分,其他都已經放在base.html裡了。所以就把需要的部份抽出,參考views/index.html來修改一下:
{% extends "base.html" %}
{% block navbar %}<a href="index.php">我的論壇</a> - {{$forum_name}}{% endblock %}
{% block content %}
<table width="100%" border="1" cellspacing="0" cellpadding="5">
<tr bgcolor="#DDEEFF">
<th>標題</th>
<th>作者</th>
<th>發表時間</th>
<th>操作</th>
</tr>
{% for row in data %}
{% if loop.index0%2==0 %}
{% set style="#EEFFEE" %}
{% else %}
{% set style="#FFFFFF" %}
{% endif %}
<tr bgcolor="{{style}}">
<td>{{$row.title}}</td>
<td>{{$row.name}}</td>
<td>{{$row.create_time}}</td>
<td>
<button onclick="document.location.href='article.php?id={{$row.id}}'">進入</button>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
然後根據目前的調整,改寫一下forum.php:
<?php
include 'bootstrap.php';
if(isset($_SESSION['user']['account'])) {
$member = true;
$name = $_SESSION['user']['name'];
} else $member = false;
$conn = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
if(!$conn)
die('mysql connection error.');
mysql_select_db(DB_NAME);
$forum_id = mysql_real_escape_string($_GET['id']);
if(isset($_SESSION['msg'])) {
$message = mysql_real_escape_string($_SESSION['msg']);
unset($_SESSION['msg']);
}
$sql = "SELECT * FROM forums WHERE id=$forum_id";
$result = mysql_query($sql, $conn);
$forum = mysql_fetch_array($result);
$sql = "SELECT a.id, a.title, a.create_time, u.name FROM articles a LEFT JOIN users u ON u.id=a.users_id WHERE forums_id=$forum_id";
$result = mysql_query($sql, $conn);
$count = 0;
$rows = array();
while($row = mysql_fetch_array($result)) {
if($count%2==0) {
$style = 'bgcolor="#EEFFEE"';
} else {
$style = 'bgcolor="#FFFFFF"';
}
$row['style'] = $style;
$rows[] = $row;
$count++;
}
$view = Fillano\Core\View::getInstance(TEMPLATE_ENGINE, 'views');
$view->assign(array(
'member'=>$member,
'name'=>$name,
'data'=>$rows,
'forum_name'=>$forum['name'],
'message'=>$message
));
$view->render('forum','html');
這是可以跑的中間版本,因為必須引入bootstrap.php,才會做class autoloading,不過mysql_connection()跟mysql_select_db()在之前的架構調整中已經從bootstrap.php中移除,所以還是要留著。同樣先用瀏覽器確認一下是否可以執行。沒問題了,就繼續小步前進。
接下來繼續拆Model,先定義好介面:
<?php
namespace Fillano\Models;
Interface IForum
{
public function getForumById($forum_id);
public function getArticleList($forum_id);
}
接著先用Mysql來實作,這樣就可以把之前的程式碼移過來使用:
<?php
namespace Fillano\Models;
class PDOForum implements IForum
{
private $pdo;
public function __construct($pdo)
{
$this->pdo = $pdo;
}
public function getForumById($forum_id)
{
$sql = "SELECT * FROM forums WHERE id=:forum_id";
$stmt = $this->pdo->prepare($sql);
if($stmt->execute(array(":forum_id"=>$forum_id))) {
return $stmt->fetch();
} else {
return null;
}
}
public function getArticleList($forum_id)
{
$sql = "SELECT a.id, a.title, a.create_time, u.name "
."FROM articles a "
."LEFT JOIN users u ON u.id=a.users_id "
."WHERE forums_id=:forum_id";
$stmt = $this->pdo->prepare($sql);
if($stmt->execute(array(":forum_id"=>$forum_id))) {
return $stmt->fetchAll();
} else {
return array();
}
}
}
確定沒問題以後,因為之後的Model架構會使用PDO,所以再把MysqlForum中的程式調整成PDO,程式就不貼了,其實跟Mysql版本差不是很多。
最後再來改Controller,這樣就需要改變原本forum.php的進入點,變成index.php?mod=forum&id=1這樣模式的URL。首先要把在forum.php中剩下的程式碼,移到Forum這個Controller中。加入Model之後,原本forum.php的程式碼會變成這樣:
<?php
include 'bootstrap.php';
if(isset($_SESSION['user']['account'])) {
$member = true;
$name = $_SESSION['user']['name'];
} else $member = false;
$forum_id = mysql_real_escape_string($_GET['id']);
if(isset($_SESSION['msg'])) {
$message = mysql_real_escape_string($_SESSION['msg']);
unset($_SESSION['msg']);
}
$model = Fillano\Core\ModelFactory::getInstance('Mysql', 'Forum');
$forum = $model->getForumById($forum_id);
$rows = $model->getArticleList($forum_id);
$view = Fillano\Core\View::getInstance(TEMPLATE_ENGINE, 'views');
$view->assign(array(
'member'=>$member,
'name'=>$name,
'data'=>$rows,
'forum_name'=>$forum['name'],
'message'=>$message
));
$view->render('forum','html');
維持forum.php的程式碼不變(究竟他還是能正常運作),然後把這些程式碼先複製到Forum Controller中,然後再修改一下:
<?php
namespace Fillano\Controllers;
use Fillano\Core\Controller;
class Forum extends Controller
{
public function index($req, $rep)
{
if(isset($_SESSION['user']['account'])) {
$member = true;
$name = $_SESSION['user']['name'];
} else $member = false;
if(isset($_SESSION['msg'])) {
$message = mysql_real_escape_string($_SESSION['msg']);
unset($_SESSION['msg']);
}
$forum_id = $req->get['id'];
$model = $this->useModel('Forum');
$forum = $model->getForumById($forum_id);
$rows = $model->getArticleList($forum_id);
$view = $rep->useView();
$view->assign(array(
'member'=>$member,
'name'=>$name,
'data'=>$rows,
'forum_name'=>$forum['name'],
'message'=>$message
));
$view->render('forum','html');
}
}
最後修改一下index.html樣板中的網址,改變forum的進入點:
{% extends "base.html" %}
{% block navbar %}我的論壇{% endblock %}
{% block content %}
<table width="100%" border="1" cellspacing="0" cellpadding="5">
<tr bgcolor="#DDEEFF">
<th>論壇名稱</th>
<th>文章數</th>
<th>最新文章</th>
<th>操作</th>
</tr>
{% for row in data %}
{% if loop.index0%2==0 %}
{% set style="#EEFFEE" %}
{% else %}
{% set style="#FFFFFF" %}
{% endif %}
<tr bgcolor="{{style}}">
<td>{{row.name}}</td>
<td>{{row.count}}</td>
<td>{{row.title}}</td>
<td>
<button onclick="document.location.href='?mod=forum&id={{row.id}}'">進入</button>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
完成以後,先清一下樣板引擎的cache,然後回到首頁,進入個別的論壇,可以看到網址不太一樣,但是畫面沒變:
按照這個方式繼續下去,應該就可以一支一支程式地把架構調整好一點。不過這些範例程式很簡單,在實際的專案中,應該會有更複雜的狀況要解決就是了。
=謝幕=
我大概是最後交卷的吧?
每天這樣思考架構,嘗試解決方案,探索新知,很充實但是也很累(每天大概會花兩三個小時以上coding或測試)...不過還是希望能表達出一個看法:寫出有可維護架構的PHP程式不難,即使開發之初用最無法維護的方式開發PHP應用軟體,也還是有可能不花太多時間就把架構改善到比較容易維護的程度,而且修改過程中,所有的程式仍然可運行。有時候要跳一大步需要很大的勇氣,但是小步前進也會達到終點。
台灣寫PHP的廠商應該蠻多的,但是還真的看到不少這類的問題,也被拉進黑洞過(接手處理...),頗有一些感觸。這三十天,總算有稍微發發怨氣